package com.linkdood.app.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.linkdood.app.constants.AuthoritiesConstants;
import com.linkdood.app.constants.ErrorCode;
import com.linkdood.app.constants.InternationalCode;
import com.linkdood.app.domain.NoticeMessage;
import com.linkdood.app.domain.Organization;
import com.linkdood.app.domain.User;
import com.linkdood.app.dto.MessageDTO;
import com.linkdood.app.dto.MessageDetailDTO;
import com.linkdood.app.dto.NoticeMessageDTO;
import com.linkdood.app.exception.GeneralException;
import com.linkdood.app.exception.InvalidParamException;
import com.linkdood.app.mapper.NoticeMessageMapper;
import com.linkdood.app.mapper.UserMapper;
import com.linkdood.app.service.NoticeMessageService;
import com.linkdood.app.service.OrganizationService;
import com.linkdood.app.service.ThirdPartyService;
import com.linkdood.app.utils.ResourceUtil;
import com.linkdood.app.vm.AccountVM;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户消息类
 *
 * @author cfj
 */
@Slf4j
@Service
public class NoticeMessageServiceImpl extends ServiceImpl<NoticeMessageMapper, NoticeMessage> implements NoticeMessageService {


    private final ThirdPartyService thirdPartyService;

    private final OrganizationService organizationService;

    private final UserMapper userMapper;

    @Value("${vrv.web.serviceHost}")
    private String serviceHost;

    @Value("${server.servlet.context-path}")
    private String contextPath;

    public NoticeMessageServiceImpl(ThirdPartyService thirdPartyService, OrganizationService organizationService, UserMapper userMapper) {
        this.thirdPartyService = thirdPartyService;
        this.organizationService = organizationService;
        this.userMapper = userMapper;
    }


    /**
     * 获取指定消息的详情
     */
    @Override
    public MessageDetailDTO getMessageDetail(String id) {
        NoticeMessage noticeMessage = this.getById(id);
        if (noticeMessage == null) {
            throw new InvalidParamException();
        }
        return messageToMessageDetailDto(noticeMessage);
    }

    /**
     * 查询通知列表
     *
     * @param title     标题
     * @param content   内容
     * @param startTime 开始时间 YYYY-MM-DDTHH:MM:SSZ
     * @param endTime   结束时间 YYYY-MM-DDTHH:MM:SSZ
     * @param pageable  分页
     */
    @Override
    public Page<MessageDetailDTO> pageNotices(String title, String content, LocalDateTime startTime, LocalDateTime endTime, Page<NoticeMessage> pageable) {

        // 此处不用lmbda 和condition 判断是因为没法对参数同时处理，会报空指针异常
        QueryWrapper<NoticeMessage> wrapper = new QueryWrapper<>();
        if (title != null) {
            wrapper.like("title", title.trim());
        }
        if (content != null) {
            wrapper.like("content", content.trim());
        }
        if (startTime != null) {
            wrapper.ge("create_time", startTime);
        }
        if (endTime != null) {
            wrapper.le("create_time", endTime);
        }
        wrapper.orderByDesc("create_time");
        Page<NoticeMessage> messagePage = baseMapper.selectPage(pageable, wrapper);

        // 转换VM
        List<MessageDetailDTO> userNoticeListList = messagePage.getRecords().stream().filter(Objects::nonNull)
            .map(this::messageToMessageDetailDto).collect(Collectors.toList());
        // 重新构造page
        Page<MessageDetailDTO> result = new Page<>();
        result.setTotal(messagePage.getTotal());
        result.setSize(messagePage.getSize());
        result.setRecords(userNoticeListList);

        return result;
    }

    private MessageDetailDTO messageToMessageDetailDto(NoticeMessage message) {
        MessageDetailDTO messageDetailDTO = new MessageDetailDTO();
        messageDetailDTO.setId(message.getId());
        messageDetailDTO.setTitle(message.getTitle());
        messageDetailDTO.setContent(message.getContent());
        messageDetailDTO.setCreateUserId(message.getFromUser());
        messageDetailDTO.setCreateUserName(message.getFromName());
        messageDetailDTO.setCreateTime(message.getCreateTime());
        return messageDetailDTO;
    }

    /**
     * 创建或者转发消息
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<MessageDetailDTO> createNotice(NoticeMessageDTO noticeDTO, AccountVM account) {
        log.debug("createNotice params noticeDTO = {}, user = {}", noticeDTO.toString(), account.toString());
        if (null == account.getUser()) {
            throw new GeneralException(ErrorCode.ERROR_ACCOUNT_NOT_BIND_USER);
        }
        boolean receiverIsEmpty = CollectionUtils.isEmpty(noticeDTO.getUsers()) && CollectionUtils.isEmpty(noticeDTO.getGroups()) &&
                (noticeDTO.getOrganizations() == null || (CollectionUtils.isEmpty(noticeDTO.getOrganizations().getCheckedUsers()) &&
                        CollectionUtils.isEmpty(noticeDTO.getOrganizations().getUncheckedUsers()) && CollectionUtils.isEmpty(noticeDTO.getOrganizations().getCheckedOrgs()) &&
                        CollectionUtils.isEmpty(noticeDTO.getOrganizations().getUncheckedOrgs())));
        if (receiverIsEmpty) {
            throw new GeneralException(ErrorCode.ERROR_NOTICE_NO_RECEIVER);
        }

        if (null == account.getRole()) {
            throw new GeneralException(ErrorCode.ERROR_ACCOUNT_PERMISSION);
        }

        // 保存用户消息通知
        MessageDTO messageDTO = new MessageDTO();
        messageDTO.setTitle(noticeDTO.getTitle());
        messageDTO.setFromUser(account.getUser().getSourceId());
        messageDTO.setFromName(account.getUser().getDisplayName());
        messageDTO.setContent(noticeDTO.getContent());
        messageDTO.setUrl(getMessageUrl(""));

        List<MessageDetailDTO> result = new ArrayList<>();
        // 获取消息发送用户
        Set<Receiver> noticeReceivers = listReceivers(noticeDTO, account.getUser().getThirdPartyId());

        // 发送用户消息
        if (CollectionUtils.isNotEmpty(noticeReceivers)) {
            messageDTO.setChatSceneType(AuthoritiesConstants.MESSAGE_USER_TYPE_PERSON);
            // 因为用户中包含部门用户，为避免用户太多，每 500 个用户用户为一组，发送一次消息
            List<List<Receiver>> noticeReceiversGroup = ResourceUtil.fixedGrouping(noticeReceivers, 500);
            for (List<Receiver> receivers : noticeReceiversGroup) {
                List<String> toUserNames = receivers.stream().map(Receiver::getName).distinct().collect(Collectors.toList());
                messageDTO.setToNames(StringUtils.join(toUserNames, ","));
                List<String> toUserIds = receivers.stream().map(Receiver::getId).distinct().collect(Collectors.toList());
                messageDTO.setToUsers(StringUtils.join(toUserIds, ","));
                NoticeMessage message = saveMessage(messageDTO, noticeDTO);
                result.add(messageToMessageDetailDto(message));
            }
        }
        // 发送群组消息
        if (CollectionUtils.isNotEmpty(noticeDTO.getGroups())) {
            messageDTO.setChatSceneType(AuthoritiesConstants.MESSAGE_USER_TYPE_GROUP);
            List<List<NoticeMessageDTO.Receiver>> groupReceiversGroup = ResourceUtil.fixedGrouping(noticeDTO.getGroups(), 500);
            for (List<NoticeMessageDTO.Receiver> groupReceivers : groupReceiversGroup) {
                List<String> toGroupIds = groupReceivers.stream().map(NoticeMessageDTO.Receiver::getId).distinct().collect(Collectors.toList());
                messageDTO.setToUsers(StringUtils.join(toGroupIds, ","));
                List<String> toGroupNames = groupReceivers.stream().map(NoticeMessageDTO.Receiver::getName).distinct().collect(Collectors.toList());
                messageDTO.setToNames(StringUtils.join(toGroupNames, ","));
                NoticeMessage message = saveMessage(messageDTO, noticeDTO);
                result.add(messageToMessageDetailDto(message));
            }
        }
        return result;
    }

    /**
     * 获取消息接收者
     */
    private Set<Receiver> listReceivers(NoticeMessageDTO noticeDTO, String thirdPartyId) {
        log.debug("createReceiver params is noticeDTO = {}", noticeDTO.toString());
        Set<Receiver> userReceivers = new HashSet<>();

        // 处理选中用户
        List<NoticeMessageDTO.ReceiverUser> users = noticeDTO.getUsers();
        if (users != null && !users.isEmpty()) {
            for (NoticeMessageDTO.ReceiverUser receiver : users) {
                Receiver noticeReceiver = new Receiver();
                noticeReceiver.setId(receiver.getSourceId());
                noticeReceiver.setName(receiver.getName());
                userReceivers.add(noticeReceiver);
            }
        }

        // 处理部门中的用户
        NoticeMessageDTO.Organization organization = noticeDTO.getOrganizations();
        if (organization == null) {
            return userReceivers;
        }
        // 处理部门中选中的用户
        List<NoticeMessageDTO.ReceiverUser> checkedUsers = organization.getCheckedUsers();
        if (CollectionUtils.isNotEmpty(checkedUsers)) {
            for (NoticeMessageDTO.ReceiverUser receiver : checkedUsers) {
                Receiver noticeReceiver = new Receiver();
                noticeReceiver.setId(receiver.getSourceId());
                noticeReceiver.setName(receiver.getName());
                userReceivers.add(noticeReceiver);
            }
        }

        // 处理部门
        List<NoticeMessageDTO.Receiver> checkedOrgs = organization.getCheckedOrgs();
        List<NoticeMessageDTO.Receiver> uncheckedOrgs = organization.getUncheckedOrgs();
        if (CollectionUtils.isNotEmpty(checkedOrgs)) {
            List<NoticeMessageDTO.ReceiverUser> unCheckedUsers = organization.getUncheckedUsers();
            List<String> uncheckedUserId = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(unCheckedUsers)) {
                uncheckedUserId = unCheckedUsers.stream().map(NoticeMessageDTO.ReceiverUser::getSourceId).collect(Collectors.toList());
            }
            for (NoticeMessageDTO.Receiver receiver : checkedOrgs) {

                // 遍历子部门
                String orgId = receiver.getId();
                Optional<Organization> doodOrganization = organizationService.getByOrgIdAndThirdPartyId(orgId, thirdPartyId);
                if (!doodOrganization.isPresent()) {
                    continue;
                }
                getOrgUser(doodOrganization.get(), uncheckedUserId, uncheckedOrgs, userReceivers);
            }
        }
        return userReceivers;
    }

    /**
     * 获取组织下，以及子组织下的用户
     *
     * dood 数据库在设计时，就考虑到了遍历查询组织部门下所有子部门的场景，所以在设计 orgCode 字段的时候，
     * 使节点与子节点间保持了紧密的联系，如：当一个节点的 orgCode 为 10001 时， 则其子部门的 orgCode 必须以 10001 开头，比如子部门 orgCode 可能为 10001001
     * 所以想要查看某个部门下的子部门，只需要使用这样的查询语句就行：select * from DOOD_ORGANIZATION where orgCode like '10001%';
     */
    private void getOrgUser(Organization org, List<String> uncheckedUserId,
                            List<NoticeMessageDTO.Receiver> uncheckedOrgs, Set<Receiver> userReceivers) {
        String code = org.getOrgCode();
        String thirdPartyId = org.getThirdPartyId();
        if (StringUtils.isEmpty(code)) {
            return;
        }
        List<Organization> organizations = organizationService.listByOrgCodeAndThirdPartyId(code, thirdPartyId);
        List<String> uncheckedOrgIds = uncheckedOrgs.stream()
                .map(NoticeMessageDTO.Receiver::getId)
                .collect(Collectors.toList());
        // 获取所有未选中部门的 id, 其中未选中的部门下的子部门，也是未选中的
        List<String> allUncheckedOrgIds = new ArrayList<>(uncheckedOrgIds);
        for (String orgId : uncheckedOrgIds) {
            Optional<Organization> organization = organizationService.getByOrgIdAndThirdPartyId(orgId, thirdPartyId);

            if (!organization.isPresent()) {
                continue;
            }
            List<Organization> sonOrganizations = organizationService.listByOrgCodeAndThirdPartyId(organization.get().getOrgCode(), thirdPartyId);
            List<Long> sonOrganizationIds = sonOrganizations.stream()
                    .map(Organization::getOrgId)
                    .collect(Collectors.toList());
            for (Long sonOrgId : sonOrganizationIds) {
                allUncheckedOrgIds.add(String.valueOf(sonOrgId));
            }
        }

        for (Organization organization : organizations) {
            String orgId = String.valueOf(organization.getOrgId());
            // 如果是未选中的部门或者是其子部门
            if (allUncheckedOrgIds.contains(orgId)) {
                continue;
            }

            // 获取组织下的直属用户
            List<User> users = userMapper.getUserByOrgId(organization.getOrgId(), thirdPartyId);
            if (CollectionUtils.isNotEmpty(users)) {
                for (User user : users) {
                    if (uncheckedUserId.contains(user.getSourceId())) {
                        continue;
                    }
                    Receiver noticeReceiver = new Receiver();
                    noticeReceiver.setId(user.getSourceId());
                    noticeReceiver.setName(user.getDisplayName());
                    userReceivers.add(noticeReceiver);
                }
            }
        }
    }

    /**
     * 获取模板消息 url
     */
    @Override
    public String getMessageUrl(String messageId) {
        String url = serviceHost + contextPath + "/#/messageDetail?fromMessage=true&messageId=" + messageId;
        log.info("message url is {}", url);
        return url;
    }

    /**
     * 构造模板消息的内容
     */
    private String getMessageContent(AccountVM accountDTO, String title) {
        String contents;
        String username = accountDTO.getUser().getDisplayName();
        String orgName = "";
        List<AccountVM.InnerOrg> orgs = accountDTO.getUser().getOrg();
        if (CollectionUtils.isNotEmpty(orgs)) {
            List<String> orgNames = orgs.stream().map(AccountVM.InnerOrg::getName).collect(Collectors.toList());
            if (orgNames.size() >= 2) {
                orgName = StringUtils.join(orgNames, ",");
            } else {
                orgName = orgNames.get(0);
            }
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String publisher = ResourceUtil.getMessage(InternationalCode.NOTICE_PUBLISHER);
        String title2 = ResourceUtil.getMessage(InternationalCode.NOTICE_CONTENT_TITLE);
        String createdTime = ResourceUtil.getMessage(InternationalCode.NOTICE_CREATEDTIME);

        contents = publisher + "   :" + (StringUtils.isNotEmpty(orgName) ? orgName : "") + username + "\n" +
                createdTime + ":" + sdf.format(new Date()) + "\n" +
                title2 + ":" + title;
        return contents;
    }

    @Override
    public NoticeMessage saveMessage(MessageDTO messageDTO, NoticeMessageDTO noticeMessageDTO) {
        NoticeMessage message = convertMessage(messageDTO, noticeMessageDTO);
        save(message);
        return message;
    }

    private NoticeMessage convertMessage(MessageDTO messageDTO, NoticeMessageDTO noticeMessageDTO) {
        NoticeMessage message = new NoticeMessage();
        message.setTitle(messageDTO.getTitle());
        message.setContent(messageDTO.getContent());
        message.setUrl(messageDTO.getUrl());
        message.setFromUser(messageDTO.getFromUser());
        message.setFromName(messageDTO.getFromName());
        message.setToUsers(messageDTO.getToUsers());
        message.setToNames(messageDTO.getToNames());
        message.setChatSceneType(messageDTO.getChatSceneType());
        message.setType(noticeMessageDTO.getType());
        message.setStatus(0);
        message.setStartTime(LocalDateTime.now());
        message.setTitleBackgroundColor("#4cb6ea");
        message.setThirdPartyId(thirdPartyService.getDefaultThirdParty().getId());
        return message;
    }

    @Data
    static class Receiver {
        private String id;
        private String name;
    }

    @Override
    public List<NoticeMessage> getByThirdIdAndStarTime(String thirdId, String startTime, String endTime) {
        return lambdaQuery().eq(NoticeMessage::getThirdPartyId, thirdId)
                .between(NoticeMessage::getStartTime, startTime, endTime)
                .in(NoticeMessage::getStatus, 0, 2)
                .list();
    }
}
